/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://code.google.com/p/dylab/

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

#ifndef __dylab_Simulation_h__
#define __dylab_Simulation_h__

//===========================================================================================
#include <dylab/simobject/SimObject.h>
#include <dylab/simulation/EventDispatcher.h>
#include <dylab/simulation/ComCollisionHandler.h>
#include <dylab/simulation/ComGfx.h>
#include <dylab/simulation/ComInput.h>
#include <dylab/simulation/ComHaptics.h>
#include <dylab/utils/containers/HashObjContainer.h>
#include <dylab/utils/EnumIterator.h>
#include <dylab/utils/thread/GuardedObject.h>
#include <dylab/utils/thread/GuardedValue.h>

//===========================================================================================
namespace dylab {

    /** This class holds and controls every aspect of the simulation. 
	@remarks
		The simulation is responsible for creating, controlling and destroying all 
		simulation components (classes implementing the interface ISimulationComponent) 
		and simulation objects (instances of the class SimObject), 
		and to control the flow of simulation time.
    */
    class DYLAB_EXPORT Simulation 
		: public EntityDefinitionManager::EntityBase<Simulation>
    {
    public:
        /// Simulation objects container.
        class DYLAB_EXPORT SimObjects
            : public HashObjContainer<String, SimObject> 
        {
        public:
            SimObjects()
                : HashObjContainer<String, SimObject> (true)
                    { }
        };       
        //-----------------------------------------------------------------------------

	public:
		/** Registers all simulation component types.
		@remarks
			This method has to be called before creating of any simulation component begins.
		@par
			The component types are registered with the following names:
				ComGfx				-> 'Gfx'
				ComInput			-> 'Input'
				ComHaptics			-> 'Haptics'
				ComCollisionHandler	-> 'CollisionHandler'
				ComEventListener	-> 'EventListener'		 
		*/
		static void registerAllComponentTypes();

		/** Registers simulation itself and all its component types as entities for entity definition manager.
		@remarks
			This method has to be called before loading any of the entities defined below begins.
		@par
			The simulation and all its component types are registered with the following names:
				Simulation			-> 'simulation'
				ComGfx				-> 'gfx'
				ComInput			-> 'input'
				ComHaptics			-> 'haptics'
				ComCollisionHandler	-> 'collHandler'
				ComEventListener	-> 'listener'		 
		*/
		static void registerAllEntities();

    private:
        /// Event listeners container.
        typedef HashObjContainer<String, ComEventListener::Instance> EventListeners;

        /// Collision handlers container.
        typedef HashObjContainer<String, ComCollisionHandler::Instance> CollisionHandlers;

    private:

        /// Container with all simulaton objects.
        SimObjects mSimObjects;

        /// Input, haptics & gfx event dispatchers.
        ComInput::EventDispatcher mInputEventDispatcher;
        ComHaptics::EventDispatcher mHapticsEventDispatcher;
        ComGfx::EventDispatcher mGfxEventDispatcher;

        /// Simulation timer.
        Timer mTimer;

        /// Meassures time elapsed since the last update.
        real_t mElapsedTime;

        //===========================================================================================
        // Guarded values & object pointers
        //===========================================================================================

        /// Instance of a gfx component.
        ComGfx::Instance mGfx;

        /// Instance of an input component.
        ComInput::Instance mInput;

        /// Instance of a haptics component.
        ComHaptics::Instance mHaptics;

        /// Event listeners container (in contrast to event dispatchers this container owns the listeners).
        EventListeners mEventListeners;

        /// Container with instances of a collision handler components.
        CollisionHandlers mCollisionHandlers;

        /// Global force field (usually used for the gravity).
        GuardedValue<Vector3> mGlobalForce;

        /// Signalizes, that the simulation cycle is to be ended.
        GuardedValue<bool> mQuitFlag;

        /// Signalizes whether the update process of the simulation objects is paused.
        GuardedValue<bool> mSimObjectsPaused;

        /// Simulation time step.
        GuardedValue<real_t> mTimeStep;

        /// Simulation time factor.
        GuardedValue<real_t> mTimeFactor;

        /// Signalizes whether the timer should be reseted.
        GuardedValue<bool> mResetTimer;

    public:

        Simulation();
        virtual ~Simulation();

        //===========================================================================================
        // Attribute access methods
        //===========================================================================================

        inline void setGlobalForce(const Vector3 & globalForce)
                { mGlobalForce = globalForce; }
        inline Vector3 getGlobalForce() const
                { return mGlobalForce; }

        inline void setQuitFlag()
                { mQuitFlag = true; }
        inline bool getQuitFlag() const
                { return mQuitFlag; }

        inline bool areSimObjectsPaused() const
                { return mSimObjectsPaused; }

        void setTimeStep(real_t maxTimeStep) 
                { mTimeStep = maxTimeStep; }
        real_t getTimeStep() const 
                { return mTimeStep; }

        void setTimeFactor(real_t timeFactor) 
                { mTimeFactor = timeFactor; }
        real_t getTimeFactor() const 
                { return mTimeFactor; }

        //===========================================================================================
        // simulation loading methods
        //===========================================================================================

        /** Loads the simulation from the definition with the given name.
		@remarks
			The definition is searched in "simulation" group of EntityDefinitionManager,
			so both the group and the specified definition must exists.
		@param[in] _defName 
			The name of the simulation definition to load.
        */
        void loadFromDefinition(const String & _defName);

        //===========================================================================================
        // Gfx related methods
        //===========================================================================================

        /** Creates and returns a gfx component given by its name.
		@remarks			
			The component type ComGfx must be registered prior to call this method.
			The simulation will create the gfx component with the full name "<ComGfx_regName>.<_simGfxName>".
		@param[in] _simGfxName
			The name of the gfx component to create.
        @returns
			The newly created component instance.
        */
        ComGfx::Instance * createGfx(const String & _simGfxName);

        inline ComGfx::Instance * getGfx()
                { return & mGfx; }
        inline const ComGfx::Instance * getGfx() const
                { return & mGfx; }

        //===========================================================================================
        // Input related methods
        //===========================================================================================

        /** Creates and returns an input component given by its name.
		@remarks			
			The component type ComInput must be registered prior to call this method.
			The simulation will create the input component with the full name "<ComInput_regName>.<_simInputName>".
		@param[in] _simInputName
			The name of the input component to create.
        @returns
			The newly created component instance.
        */
        ComInput::Instance * createInput(const String & _simInputName);

        inline ComInput::Instance * getInput()
                { return & mInput; }
        inline const ComInput::Instance * getInput() const
                { return & mInput; }

        //===========================================================================================
        // Haptics related methods
        //===========================================================================================

        /** Creates and returns a haptics component given by its name.
		@remarks			
			The component type ComHaptics must be registered prior to call this method.
			The simulation will create the haptics component with the full name "<ComHaptics_regName>.<_simHapticsName>".
		@param[in] _simHapticsName
			The name of the haptics component to create.
        @returns
			The newly created component instance.
        */
        ComHaptics::Instance * createHaptics(const String & _simHapticsName);

        inline ComHaptics::Instance * getHaptics()
                { return & mHaptics; }
        inline const ComHaptics::Instance * getHaptics() const
                { return & mHaptics; }

        //===========================================================================================
        // event listeners related methods
        //===========================================================================================

        /** Creates and returns a event listener component given by its name.
		@remarks			
			The component type ComEventListener must be registered prior to call this method.
			The simulation will create the event listener component with 
			the full name "<ComEventListener_regName>.<_simGfxName>".
		@param[in] _listenerName
			The name of the event listener under which will be stored in the simulation.
		@param[in] _listenerComName
			The name of the event listener component to create.
        @returns
			The newly created component instance.
        */
        ComEventListener::Instance * createEventListener(const String & _listenerName, 
			const String & _listenerComName);

        /** Destroy previously created event listener with the given name.
        @param[in] _listenerName
			The name of the event listener to destroy under which it was stored in the simulation.
        */
        void destroyEventListener(const String & _listenerName);

        inline ComEventListener::Instance * getEventListener(const String & _listenerName)
                { return mEventListeners.get(_listenerName); }
        inline const ComEventListener::Instance * getEventListener(const String & _listenerName) const
                { return mEventListeners.get(_listenerName); }

        //===========================================================================================
        // event dispatchers related methods
        //===========================================================================================

        inline ComInput::EventDispatcher * getInputEventDispatcher()
                { return & mInputEventDispatcher; }
        inline const ComInput::EventDispatcher * getInputEventDispatcher() const 
                { return & mInputEventDispatcher; }

        inline ComHaptics::EventDispatcher * getHapticsEventDispatcher()
                { return & mHapticsEventDispatcher; }
        inline const ComHaptics::EventDispatcher * getHapticsEventDispatcher() const
                { return & mHapticsEventDispatcher; }

        inline ComGfx::EventDispatcher * getGfxEventDispatcher()
                { return & mGfxEventDispatcher; }
        inline const ComGfx::EventDispatcher * getGfxEventDispatcher() const
                { return & mGfxEventDispatcher; }

        //===========================================================================================
        // SimObject related methods
        //===========================================================================================

        /** Creates new empty simulation object with the given name.
		@param[in] _simObjectName
			The name under which the newly created object will be stored in the simulation.
		@returns
			The newly create instance of SimObject
        */
        SimObject * createSimObject(const String & _simObjectName);

        /** Destroys previously created simulation object with the given name.
        @param[in] _simObjectName
			The name of the simulation object to destroy under which it was stored in the simulation.
         */
        void destroySimObject(const String & _simObjectName);

        inline SimObjects::Reader simObjectReader() const
                { return SimObjects::Reader(mSimObjects); }
        inline SimObjects::Iterator simObjectIterator()
                { return SimObjects::Iterator(mSimObjects); }
        inline const SimObjects::ConstIterator simObjectIterator() const
                { return SimObjects::ConstIterator(mSimObjects); }

        //===========================================================================================
        // Collision handler methods
        //===========================================================================================

        /** Creates and returns a collision handler component given by its name.
		@remarks			
			The component type ComCollisionHandler must be registered prior to call this method.
			The simulation will create the collision handler component with 
			the full name "<ComCollisionHandler_regName>.<_simGfxName>".
		@param[in] _handlerName
			The name of the collision handler under which will be stored in the simulation.
		@param[in] _handlerComName
			The name of the collision handler component to create.
        @returns
			The newly created component instance.
        */
        ComCollisionHandler::Instance * createCollisionHandler(const String & _handlerName, 
			const String & _handlerComName);

        /** Destroy previously created collision handler with the given name.
        @param[in] _handlerName
			The name of the collision handler to destroy under which it was stored in the simulation.
        */
        void destroyCollisionHandler(const String & _handlerName);

        inline ComCollisionHandler::Instance * getCollisionHandler(const String & _handlerName)
                { return mCollisionHandlers.get(_handlerName); }
        inline const ComCollisionHandler::Instance * getCollisionHandler(const String & _handlerName) const
                { return mCollisionHandlers.get(_handlerName); }

        //===========================================================================================
        // Statistics methods
        //===========================================================================================
		
        /** Writes the current statistical informations into the current log.
         */
        void logStatistics();

        //===========================================================================================
        // Update methods
        //===========================================================================================

        /** Updates all simulation objects.
		@remarks			
			According to the elapsed time and current values of attributes mTimeStep and mTimeFactor,
			performs the corresponding number of updates of all simulation objects.
			One update consists of applying model technique and integrator to all simulation objects
			followed by collision handling.		
		@param[out] _techniqueTime
			Retrieves the total time in seconds that all simulation objects have spent on computation of technique.
		@param[out] _integratorTime
			Retrieves the total time in seconds that all simulation objects have spent on computation of integration.
		@param[out] _colhandlTime
			Retrieves the total time in seconds that the simulation has spent on handling the collisions.
        */
        void updateSimObjects(real_t * _techniqueTime = NULL, real_t * _integratorTime = NULL, 
			real_t * _colhandlTime = NULL);

        /** Temporarily pauses/resumes updating all simulation objects.
		@param[in] _pause
			Signalizes whether the simulation objects should be paused or resumed.
        */
        void pauseSimObjects(bool _pause);

        /** Takes care of rendering whole simulation scene.
		@remarks
			The gfx component should be created (using the createGfx() method) prior calling this method.
		@param[out] _renderingTime
			Retrieves the total time in seconds that the simulation has spent on rendering the scene.
        */
        void renderFrame(real_t * _renderingTime = NULL);

        /** Takes care of capturing state of common input devices (mouse, keyboard).
		@remarks
			The input component should be created (using the createInput() method) prior calling this method.
		@param[out] _inputTime
			Retrieves the total time in seconds that the simulation has spent on capturing the input.
        */
        void captureInput(real_t * _inputTime = NULL);

        /** Takes care of updating the haptic devices.
		@remarks
			The haptics component should be created (using the createHaptics() method) prior calling this method.
		@param[out] _hapticsTime
			Retrieves the total time in seconds that the simulation has spent on updating the haptic devices.
        */
        void updateHaptics(real_t * _hapticsTime = NULL);

        /** Dispatches all events from in all event queues to proper event listener components.
		@param[out] _eventsTime
			Retrieves the total time in seconds that the simulation has spent on dispatching the events.
        */
        void dispatchAllEvents(real_t * _eventsTime = NULL);

    private:
        void _updateAllSimObjects(real_t _timeStep, real_t * _techniqueTime = NULL, 
			real_t * _integratorTime = NULL);
        void _handleAllCollisions(real_t * _colhandlTime = NULL);
    };
}
#endif // __dylab_Simulation_h__
//===========================================================================================
